#ifndef INDEX_H_
#define INDEX_H_
#include <iostream>
#include <fstream>
#include <map>
#include <string>
#include <vector>
#include "../SocketsAndThreads/mutex.h"
#include "../SocketsAndThreads/errors.h"
#define MAX_LINE_LENGTH 500
#define DELIMITER "//"
#define ENCRYPTED_DELIMITER "00"
using namespace std;
class Index {
private:
	fstream file;
	map<string, string> rows;
	string index_path;
	Mutex m;

	//Encripta con el metodo caesar un string
	void Encrypt(string* s) {
		for (string::iterator it = s->begin(); it != s->end(); ++it) {
			if (*it != 255) {
				*it = *it + 1;
			} else {
				*it = 0;
			}
		}
	}

	//Desencripta con el metodo caesar un string
	void Desencrypt(string* s) {
		for (string::iterator it = s->begin(); it != s->end(); ++it) {
			if (*it != 0) {
				*it = *it - 1;
			} else {
				*it = 255;
			}
		}
	}

public:
	Index() {

	}
	Index(string* index_path) {
		this->index_path = *index_path;
	}
	//Devuelve un puntero al mutex del indice
	Mutex* GetMutex() {
		return &this->m;
	}

	bool IsEmpty() {
		return (this->rows.empty());
	}

	void setIndexPath(string* index_path) {
		this->index_path = *index_path;
	}

	//Se levanta el archivo de indice de disco
	void LoadFromDisk() {
		map<string, string> new_rows;
		file.open(index_path.c_str(), ios::in);
		if (file.is_open()) {
			while (!file.eof()) {

				string key1;
				string key2;
				char aux[MAX_LINE_LENGTH];
				file.getline(aux, MAX_LINE_LENGTH);
				string buffer(aux);
				if (!file.eof()) {
					unsigned int pos = buffer.find(ENCRYPTED_DELIMITER);
					key1 = buffer.substr(0, pos);
					Desencrypt(&key1);
					key2 = buffer.substr(pos + 2);
					Desencrypt(&key2);
					pair<string, string> p(key1, key2);
					new_rows.insert(p);
				}

			}
			rows = new_rows;
			file.close();
		} else {

			throw FileNotExist();

		}

	}

	//Guarda los cambios hechos a disco
	void SaveToDisk() {
		file.open(index_path.c_str(), ios::out);

		for (std::map<string, string>::iterator it = rows.begin();
				it != rows.end(); ++it) {
			string first(it->first);
			Encrypt(&first);
			string second = (it->second);
			Encrypt(&second);
			file << first;
			file << ENCRYPTED_DELIMITER;
			file << second;
			file << endl;
		}
		file.close();

	}

	map<string, string> GetMappedIndex() {
		return rows;
	}

	//Determina si existe una clave
	bool KeyExists(string* key) {
		return !(rows.find(*key) == rows.end());
	}

	//Borra una clave y su valor asociado
	void DeleteKey(string* key) {
		rows.erase(*key);
	}

	//Agrega una nuevo par clave-valor
	void AddKey(string* first, string* second) {
		if (!KeyExists(first)) {
			pair<string, string> p(*first, *second);
			rows.insert(p);
		}
	}

	//Edita el valor de una clave existente
	void EditKey(string* old_first, string* new_second) {
		if (KeyExists(old_first)) {
			rows.find(*old_first)->second = *new_second;
		}
	}

	//Imprimir el indice
	void Print() {
		for (std::map<string, string>::iterator it = rows.begin();
				it != rows.end(); ++it) {
			cout << it->first << " " << it->second << endl;
		}
	}

	//Obtiene un par, en base a su clave
	pair<string, string> GetPair(string* first_key) {
		string first("//");
		string second("//");
		if (KeyExists(first_key)) {
			map<string, string>::iterator it = rows.find(*first_key);
			first = it->first;
			second = it->second;
		}
		pair<string, string> p(first, second);
		return p;
	}

	//Obtiene el valor de una clave
	string GetSecond(string* first) {
		return (GetPair(first)).second;

	}

	//Crea un indice a partir de un map
	void CreateFromMap(map<string, string>* m_) {
		this->rows = *m_;
		SaveToDisk();
	}

	//Obtiene diferencias entre 2 indices, utilizado para polling
	vector<string> Differences(map<string, string>& m_) {
		vector<string> files_to_get;
		vector<string> files_to_send;
		map<string, string>::iterator i = m_.begin();
		map<string, string>::iterator j;
		for (map<string, string>::iterator endi = m_.end(); i != endi; i++) {
			bool exist = false;
			j = rows.begin();
			for (map<string, string>::iterator endj = rows.end(); j != endj;
					j++) {
				if ((i->first == j->first) and (i->second == j->second)) {
					exist = true;
					break;
				} else if (i->first == j->first) {
					break;
				}
			}
			if (not exist) {
				files_to_get.push_back(i->first);
			}
		}

		return files_to_get;
	}
};

#endif /* INDEX_H_ */
